require 'webmachine/events'
require 'webmachine/trace/resource_proxy'
require 'webmachine/trace/fsm'
require 'webmachine/trace/pstore_trace_store'
require 'webmachine/trace/trace_resource'
require 'webmachine/trace/listener'

module Webmachine
  # Contains means to enable the Webmachine visual debugger.
  module Trace
    module_function

    # Classes that implement storage for visual debugger traces.
    TRACE_STORES = {
      memory: Hash,
      pstore: PStoreTraceStore
    }

    DEFAULT_TRACE_SUBSCRIBER = Webmachine::Events.subscribe(
      /wm\.trace\..+/,
      Webmachine::Trace::Listener.new
    )

    # Determines whether this resource instance should be traced.
    # @param [Webmachine::Resource] resource a resource instance
    # @return [true, false] whether to trace the resource
    def trace?(resource)
      # For now this defers to the resource to enable tracing in the
      # initialize method. At a later time, we can add tracing at the
      # Application level, perhaps.
      resource.trace?
    end

    # Records a trace from a processed request. This is automatically
    # called by {Webmachine::Trace::ResourceProxy} when finishing the
    # request.
    # @api private
    # @param [String] key a unique identifier for the request
    # @param [Array] trace the raw trace generated by processing the
    #   request
    def record(key, trace)
      trace_store[key] = trace
    end

    # Retrieves keys of traces that have been recorded. This is used
    # to present a list of available traces in the visual debugger.
    # @api private
    # @return [Array<String>] a list of recorded traces
    def traces
      trace_store.keys
    end

    # Fetches a given trace from the trace store. This is used to
    # send specific trace information to the visual debugger.
    # @api private
    # @param [String] key the trace's key
    # @return [Array] the raw trace
    def fetch(key)
      trace_store.fetch(key)
    end

    # Sets the trace storage method. The first parameter should be a
    # Symbol, followed by any additional options for the
    # store. Defaults to :memory, which is an in-memory Hash.
    # @example
    #   Webmachine::Trace.trace_store = :pstore, "/tmp/webmachine.trace"
    def trace_store=(*args)
      @trace_store = nil
      @trace_store_opts = args
    end
    self.trace_store = :memory

    def trace_store
      @trace_store ||= begin
        opts = Array(@trace_store_opts).dup
        type = opts.shift
        TRACE_STORES[type].new(*opts)
      end
    end
    private :trace_store

    # Sets the trace listener objects.
    # Defaults to Webmachine::Trace::Listener.new.
    # @param [Array<Object>] listeners a list of event listeners
    # @return [Array<Object>] a list of event subscribers
    def trace_listener=(listeners)
      Webmachine::Events.unsubscribe(DEFAULT_TRACE_SUBSCRIBER)

      Array(listeners).map do |listener|
        Webmachine::Events.subscribe(/wm\.trace\..+/, listener)
      end
    end
  end
end
